/*
 * Copyright (C) 2012  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.swing.util;

import java.awt.event.ActionEvent;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;

import javax.swing.AbstractButton;
import javax.swing.SwingWorker;

/**
 * BackgroundAction : a generic Swing action coupled with a {@link SwingWorker}
 * in order to perform the process in a Swing-safe way.
 * 
 * <p>
 * The non-final protected methods are meant to be overriden in order to handle
 * the action process.
 * </p>
 * 
 * @author Romain DUBOIS
 * @param <T>
 *            The action's result type
 * @param <V>
 *            The intermediate data type
 */
public class BackgroundAction<T, V> extends NeutralAction {

    /** Serial version */
    private static final long serialVersionUID = 1L;

    /** The active worker */
    private final AtomicReference<SwingWorker<T, V>> mWorker;

    /**
     * Constructor.
     */
    public BackgroundAction() {
        super();

        mWorker = new AtomicReference<SwingWorker<T, V>>();
    }

    /**
     * Constructor.
     * 
     * @param pButton
     *            The button to take data from
     */
    public BackgroundAction(final AbstractButton pButton) {
        super(pButton);

        mWorker = new AtomicReference<SwingWorker<T, V>>();
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    @Override
    public void actionPerformed(@SuppressWarnings("unused") final ActionEvent pE) {
        setEnabled(false);

        // Create and launch the worker
        new SwingWorker<T, V>() {

            @Override
            protected T doInBackground() throws Exception {
                if (setWorker(this)) {
                    return BackgroundAction.this.doInBackground();
                }
                return null;
            }

            @Override
            protected void done() {
                if (getWorker() == this) {
                    try {
                        BackgroundAction.this.done(get());
                    } catch (final ExecutionException e) {
                        BackgroundAction.this.handleException(e.getCause());
                    } catch (final InterruptedException e) {
                        BackgroundAction.this.handleException(e);
                    } finally {
                        setEnabled(true);
                        unsetWorker(this);
                    }
                }
            }

            @Override
            protected void process(final List<V> pChunks) {
                BackgroundAction.this.process(pChunks);
            }

        }.execute();
    }

    /**
     * Handle an exception during the process (inside the EDT).
     * 
     * @param pException
     *            The process exception
     */
    protected void handleException(final Throwable pException) {
        // Override me
        final Thread lThread = Thread.currentThread();
        lThread.getUncaughtExceptionHandler().uncaughtException(lThread, pException);
    }

    /**
     * Get the worker.
     * 
     * @return The worker
     */
    protected final SwingWorker<T, V> getWorker() {
        return mWorker.get();
    }

    /**
     * Computes a result, or throws an exception if unable to do so.
     * 
     * <p>
     * Note: this method is executed in a background thread.
     * </p>
     * 
     * @return The computed result
     * @throws Exception
     *             If unable to compute a result
     * 
     * @see SwingWorker
     */
    @SuppressWarnings("unused")
    protected T doInBackground() throws Exception {
        // Override me
        return null;
    }

    /**
     * Receives data chunks from the {@code publish} method asynchronously on
     * the <i>Event Dispatch Thread</i>.
     * 
     * @param pChunks
     *            Intermediate results to process
     * 
     * @see SwingWorker
     */
    @SuppressWarnings("unused")
    protected void process(final List<V> pChunks) {
        // Override me
    }

    /**
     * Executed on the <i>Event Dispatch Thread</i> after the
     * {@code doInBackground} method is finished. The default implementation
     * does nothing. Subclasses may override this method to perform completion
     * actions on the <i>Event Dispatch Thread</i>. Note that you can query
     * status inside the implementation of this method to determine the result
     * of this task or whether this task has been cancelled.
     * 
     * @param result
     *            The process result
     * 
     * @see #doInBackground
     * @see #getWorker()
     * @see SwingWorker#isCancelled()
     * @see SwingWorker#get
     */
    protected void done(@SuppressWarnings("unused") final T result) {
        // Override me
    }

    /**
     * Change the worker if none is active.
     * 
     * @param pWorker
     *            The worker to set
     * @return If no worker was active
     */
    boolean setWorker(final SwingWorker<T, V> pWorker) {
        return mWorker.compareAndSet(null, pWorker);
    }

    /**
     * Remove the current worker.
     * 
     * @param pWorker
     *            The expected current worker
     * @return If the worker was active
     */
    boolean unsetWorker(final SwingWorker<T, V> pWorker) {
        return mWorker.compareAndSet(pWorker, null);
    }
}
